第11章 高级绘图(III)shader着色器
本章是高级绘图的最后一章,将介绍一个重磅炸弹:shader着色器。有了它,你几乎能实现你能想象到的任何绘图效果。当然,它并不是很容易用,无论在语言上还是数学思想上。
高级绘图(III)
什么是shader
shader在love中是一个工具,使用它可以直接的跟GPU进行交流,从而十分高效的绘制出各种像素级别的效果。
一般而言,你的游戏代码是运行在cpu上的,cpu最大的问题在于它是串行的,也就是它将按顺序一个一个命令运行。而对于数据吞吐比较大的运算,比如图片数据的实时修改,它的效率并不高。然而gpu(显卡)的特点是并行操作,也就是说,它可以并列的处理图片的每一个像素。由于架构上的差异,gpu特别适合并发的运算。所以,在写shader时的核心就是并行。
有了shader你可以做很多事情,比如ps中所有的图层效果:模糊,内外发光,艺术化,去色,描边,扭曲等等。还可以实现3d,实时光照,影子,反射和折射,等等光影效果。
什么是GLSL
GLSL或者OpenGL Shading Language;就是love绘图核心opengl使用的着色器语言,这是个类C的语言,习惯于lua很不相似,因此,如果你曾经没有接触过类C语言,比如C,C++,C#之类的话,可能还需要学一些类C的基本语法。love在boot启动时,会尝试对GLSL进行编译,如果编译失败将显示shader源码的行号和错误信息。如果编译成功,运行中它讲不会产生任何的除了图像显示外的输出。因此对glsl进行调试比较困难。
love的GLSL与原版的区别
float number
sampler2D Image
uniform extern
texture2D(tex, uv) Texel(tex, uv)
love几乎与原版glsl相同,只是因为与love使用的关键字以及lua关键字相协调,采用了一些关键字的修改,实际上及时你用原版的,一般情况下也不会报错。
love中使用shader代码是以string的形式传入到newShader中的。当然,你也可以单独写,然后用love.filesystem.read()来读取。目前版本也至此直接传入文件名即可。
love中的shader基本框架
love目前支持顶点着色器(vertex shader)和片元作色器(pixel shader),我们下面分别介绍。
顶点着色器
顶点着色器的作用是确定一个像素绘制的位置。
下面是一个标准顶点着色器代码
参数 transform_projection是一个四维矩阵,它实际包含了mat4 TransformMatrix和mat4 ProjectionMatrix。transform指的是诸如love.graphics.transform等函数导致的坐标变换。projection指的是投影矩阵,2d引擎较为简单,是正交投影,而如果你需要3d视角的话,就要使用透视投影。但是,love2d的所有绘图相关的z值都没有接口让你去修改,因此无法进行3d化。而且没有3d所必须的深度检测和面剔除。当然你可以借助钩子来取得opengl的指针来进行其他操作。关于Mat4,它既可以表示一个状态,也可以表示一种变换。相关知识,请自行学习。
参数vertex_position指的是像素所在顶点的位置,它是个四维数,前两维是x,y,后面用不上。它是某个像素在屏幕上的绝对坐标。
返回值是经过矩阵变换后的像素屏幕绝对坐标。
使用顶点着色器能够改变图片的绘制位置,绘制结构等。
片元着色器(色彩着色器)
片元着色器的作用是确定某一个像素将被绘制为什么颜色。
下面是一个标准片元着色器代码
参数color就是你所设置的颜色了,通过love.graphics.setColor
texture就是被shader绑定的drallcall所使用的贴图
texture_coords是当前像素在贴图上的相对位置,取值(0,1)
texture_coords该像素屏幕上的绝对坐标。
他的返回值是一个vec4颜色,分别是r,g,b,a.
这里有一个重要的函数Texel,它可以取一个texture贴图的任意一点颜色。所以标准的返回值就是从这个图像取对应位置的颜色。
多canvas片元着色器
对于位于多个canvas内部,(setCanvas的嵌套),可以使用下面的片元着色器来对每一层canvas进行操作。
void effects(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords)
{
love_Canvases[0] = color;
love_Canvases[1] = color + vec4(0.5);
}
这里love_Canvases是love引擎内建的一个队列,代表了每一层你所设置的canvas,与上面不同的是,它不以返回值作为颜色,而是直接对love_Canvases进行赋值。注意!你必须对每一个canvas片元颜色赋值,如果跳过了某些你设定的canvas会导致那个位置没有任何颜色,会出现系统错误。
内建变量
love为了方便shader的使用,内建了一些变量,这些变量不需要声明自动赋值,当然你可以对这些值进行更改。
mat4 TransformMatrix 变形矩阵
mat4 ProjectionMatrix 投射矩阵
mat4 TransformProjectionMatrix 变形投射矩阵
vec4 VaryingTexCoord 这个值作为片元着色器对应的贴图相对位置。
vec4 VaryingColor 这个值作为gamma矫正过的颜色传入到片元着色器
vec4 love_ScreenSize love窗体大小
vec4 VertexPosition 在顶点着色器对应参数
vec4 VertexTexCoord 片元着色器对应参数,可以在顶点着色器使用
vec4 VertexColor 片元着色器对应参数,可以在顶点着色器使用
vec4 ConstantColor 片元着色器对应参数,可以在顶点着色器使用
vec4 array love_Canvases[] canvas渲染队列
shader的外部变来的定义和赋值
shader可以从外部取得参数。需要在shader代码中使用extern关键字来声明。在love代码中,使用shader:send(name,value)的方式来赋值。name为shader代码中声明的变量名。value解释如下:
目前,love支持的可传入外部变量类型有:
整形 int 必须使用 sendInt来代替send,因为lua的数值都是浮点数。
向量 vector 包括vec2,vec3,vec4等,发送数据格式为形如{1,2,3,4};注意vector有很多代称。比如x,y,z,w;r,g,b,a等,他们和[1],[2],[3],[4]具有同等效力。
矩阵 matrix 包括mat2,mat3,mat4等,发送数据格式为
贴图 texture 可以是image对象或canvas对象,直接发送即可。
布尔值 boolean 这个就跟lua 一样了, true/false
如果你发送的是一个队列,那么就在参数里继续添加即可。如 shader:send(“arr4”,v1,v2,v3,v4)
你可以使用shader:getExternVariale来重新获取上面发送的数值。
但是,在shader代码内部,是不能更改extern关键字声明的变量的,只能作为右值使用。所以原版的extern叫uniform。^^
shader的代码过程
我们用一个案例来讲解shader的代码过程。
首先定义shader,假设code代码为上面写的标准代码(无任何效果)。然后在绘制前,发送必要的变量。在draw的过程中用shader开关把draw(image)包括进去就可以了。
在shader内部,对于image的每一个像素,它都经历者下面的过程:
- 进入顶点着色器,根据当前的绘制矩阵来计算出绘制屏幕的位置。
- 进入片元着色器,根据当前像素对于image图片上的相对位置的颜色数据以及由setColor和gamma教程后的颜色合并计算,得出当前像素的颜色。
- 在屏幕或者绘制对象的位置用颜色进行填充。
对于image的所有像素,这个过程(渲染管线)是并列的,没有先后顺序的,你可以理解他们同时开启,同时结束。因此,一个像素的颜色的改变不会对其他颜色改变进行任何干扰,所有的数值也都是固定的,方式也是固定的。
如果对上述讲解不理解的,没关系,因为你们尚未接触。上面的东西,你可以在看了一些例子以后回头再去理解。希望你们有时间百度GLSL相关内容,可以稍微看看shadertoy上的案例,但是那个网站的东西都比较复杂,没有必要深入。
代码时间
我们下面尝试利用shader来做出一些最简单的效果,以便让各位理解shader为何物。
设计阶段
- 用顶点着色器来模拟绘制位置。
- 使用片元着色器绘制一个矩形。
- 使用片元着色器加深logo图案的颜色。
- 使用片元着色器,使logo在屏幕的左侧正常,在屏幕的右侧为反色。
由于上面内容在设计角度都比较简单,直接在代码中讲解。
程序阶段
绘制位置
很容易理解,但是这里注意的是要用叠加而非直接赋值,因为vertex_position并非直接对应屏幕坐标。我们导入了坐标100,100作为图片的位置,在绘制时0,0就相当于实际屏幕的100,100 因为有叠加。
绘制矩形
我们发送rect的数据为x,y,w,h,作为一个四元数,当然,也可以用number的array。在绘图阶段,我们可以看出,当屏幕坐标在矩形的范围内,我们绘制空颜色,否则绘制黄色。
色彩加深
实际上,就是把图片的所有r,g,b减半;就达到了颜色加深的目的了。其中texcolor.rgb的用法相当于取其中的前三位作为vec3进行计算。
区域变色
取反色,直接用1减去相应的通道值即可,通过判断该像素是否在屏幕右侧,如果在,则取反色,否则不变。
作业
- 用着色器绘制一个圆环。
- 用这个圆环作为镂空,绘制一个图片。
- 简单模糊(一个像素的颜色是其周围4点颜色的平均值)
- 制作一个雪花屏幕(老电视那种无信号屏幕),使用rnd函数
- 设计一个简单的畸变,实现水波纹的效果。(就是实际点到圆心的距离和取色点偏移按正弦函数排布)
本章代码
|
|